package org.homonoia.controller.net;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.AbstractMap;
import java.util.AbstractMap.SimpleEntry;

import org.apache.log4j.Logger;
import org.homonoia.controller.core.ControllerNodeService;
import org.homonoia.model.core.NodeStatus;
import org.homonoia.model.query.Query;
import org.homonoia.model.query.QueryID;
import org.homonoia.model.query.QueryResults;
import org.homonoia.model.query.QueryStatus;
import org.homonoia.model.task.TaskResults;

public class SocketConnection implements Runnable {

    private final static Logger log = Logger.getLogger(SocketConnection.class);

    private ControllerNodeService masterNodeService = null;
    private Socket incomingSock = null;
    private BufferedReader in = null;

    public SocketConnection(Socket clientSock, ControllerNodeService masterNodeService) {
	this.masterNodeService = masterNodeService;
	this.incomingSock = clientSock;
    }

    public void run() {
	String request = null;

	try {
	    in = new BufferedReader(new InputStreamReader(incomingSock.getInputStream()));
	    request = in.readLine();

	    handleRequest(request);

	} catch (Exception e) {
	    log.error(e.getMessage(), e);
	}
    }

    private void handleRequest(final String request) {
	if (request.contains("submission")) {
	    sendHeaderResponse("OK");
	    createQueuedJob();
	} else if (request.contains("status")) {
	    sendHeaderResponse("OK");
	    getJobStatus();
	} else if (request.contains("results")) {
	    sendHeaderResponse("OK");
	    getJobResults();
	} else if (request.contains("update")) {
	    updateNode();
	} else if (request.contains("search")) {
	    addSearchResults();
	} else if (request.contains("join")) {
	    addJobResults();
	} else {
	    sendHeaderResponse("BAD REQUEST");
	}
    }

    private void sendHeaderResponse(final String response) {
	PrintWriter out = null;
	try {
	    out = new PrintWriter(incomingSock.getOutputStream(), true);
	    out.println("Accept: " + response);
	} catch (IOException e) {
	    log.error(e.getMessage(), e);
	}
    }

    private void createQueuedJob() {
	ObjectInputStream ois = null;
	ObjectOutputStream oos = null;

	try {
	    ois = new ObjectInputStream(incomingSock.getInputStream());
	    Query job = (Query) ois.readObject();

	    QueryID id = masterNodeService.getJobQueue().newJob(job);

	    oos = new ObjectOutputStream(incomingSock.getOutputStream());
	    oos.writeObject(id);
	} catch (ClassNotFoundException e) {
	    log.error(e.getMessage(), e);
	} catch (IOException e) {
	    log.error(e.getMessage(), e);
	}
    }

    private void getJobStatus() {
	ObjectInputStream ois = null;
	ObjectOutputStream oos = null;

	try {
	    ois = new ObjectInputStream(incomingSock.getInputStream());
	    QueryID job = (QueryID) ois.readObject();

	    QueryStatus status = masterNodeService.getJobQueue().getJobStatus(job);

	    oos = new ObjectOutputStream(incomingSock.getOutputStream());
	    oos.writeObject(status);

	} catch (ClassNotFoundException e) {
	    log.error(e.getMessage(), e);
	} catch (IOException e) {
	    log.error(e.getMessage(), e);
	}
    }

    private void getJobResults() {
	ObjectInputStream ois = null;
	ObjectOutputStream oos = null;

	try {
	    ois = new ObjectInputStream(incomingSock.getInputStream());
	    QueryID job = (QueryID) ois.readObject();

	    QueryResults history = masterNodeService.getJobQueue().getJobResults(job);

	    oos = new ObjectOutputStream(incomingSock.getOutputStream());
	    oos.writeObject(history);

	} catch (ClassNotFoundException e) {
	    log.error(e.getMessage(), e);
	} catch (IOException e) {
	    log.error(e.getMessage(), e);
	}
    }

    private void updateNode() {
	String request = null;

	try {
	    request = in.readLine();

	    if (request.contains("AVAILABLE")) {
		masterNodeService.getWorkerManager().updateWorkerStatus(
			incomingSock.getInetAddress().getHostAddress(), NodeStatus.AVAILABLE);
	    } else if (request.contains("BUSY")) {
		masterNodeService.getWorkerManager().updateWorkerStatus(
			incomingSock.getInetAddress().getHostAddress(), NodeStatus.BUSY);
	    } else {
		masterNodeService.getWorkerManager().updateWorkerStatus(
			incomingSock.getInetAddress().getHostAddress(), NodeStatus.UNKNOWN);
	    }
	} catch (Exception e) {
	    log.error(e.getMessage(), e);
	} finally {
	    try {
		incomingSock.close();
	    } catch (IOException e) {
		log.error(e.getMessage(), e);
	    }
	}
    }

    @SuppressWarnings("unchecked")
    private void addSearchResults() {
	ObjectInputStream ois = null;

	try {
	    ois = new ObjectInputStream(incomingSock.getInputStream());
	    log.info("Pre READ");
	    AbstractMap.SimpleEntry<QueryID, TaskResults> pair = (SimpleEntry<QueryID, TaskResults>) ois
		    .readObject();
	    log.info("POST READ");

	    masterNodeService.getJobQueue().addTaskResults(pair.getKey(), pair.getValue());

	} catch (ClassNotFoundException e) {
	    log.error(e.getMessage(), e);
	} catch (IOException e) {
	    log.error(e.getMessage(), e);
	} finally {
	    try {
		incomingSock.close();
	    } catch (IOException e) {
	    }
	}
    }

    @SuppressWarnings("unchecked")
    private void addJobResults() {
	ObjectInputStream ois = null;
	try {
	    log.info("Pre READ");
	    ois = new ObjectInputStream(incomingSock.getInputStream());
	    AbstractMap.SimpleEntry<QueryID, QueryResults> pair = (SimpleEntry<QueryID, QueryResults>) ois
		    .readObject();
	    log.info("POST READ");

	    masterNodeService.getJobQueue().addJobResults(pair.getKey(), pair.getValue());

	} catch (ClassNotFoundException e) {
	    log.error(e.getMessage(), e);
	} catch (IOException e) {
	    log.error(e.getMessage(), e);
	} finally {
	    try {
		incomingSock.close();
	    } catch (IOException e) {
	    }
	}
    }
}
